package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"
)

type (
	DBConfig struct {
		Host     string            `json:"host"`
		Port     int               `json:"port"`
		Username string            `json:"username"`
		Password string            `json:"password"`
		DBName   string            `json:"db_name"`
		MaxIdle  int               `json:"max_idle"`
		MaxOpen  int               `json:"max_open"`
		Params   map[string]string `json:"params"`
	}

	RedisConfig struct {
		Enabled   bool   `json:"enabled"`
		Host      string `json:"host"`
		Port      int    `json:"port"`
		Password  string `json:"password"`
		DefaultDB int    `json:"default_db"`
		KeyPrefix string `json:"key_prefix"`
	}

	ServerConfig struct {
		Port                   int    `json:"port"`
		ReadTimeout            int    `json:"read_timeout"`
		ReadHeaderTimeout      int    `json:"read_header_timeout"`
		WriteTimeout           int    `json:"write_timeout"`
		IdleTimeout            int    `json:"idle_timeout"`
		MaxHeaderBytes         int    `json:"max_header_bytes"`
		MaxContentLength       int64  `json:"max_content_length"`
		MaxLoggedContentLength int    `json:"max_logged_content_length"`
		Secret                 string `json:"secret"`
	}

	LoggerConfig struct {
		Level               string   `json:"level"`
		Format              string   `json:"format"`
		ApplicationName     string   `json:"application_name"`
		ConfidentialFields  []string `json:"confidential_fields"`
		HookLevels          []string `json:"hook_levels"`
		HookMaxThread       int      `json:"hook_max_thread"`
		HookMaxBuffer       int      `json:"hook_max_buffer"`
		HookConnMaxLifeTime int      `json:"hook_conn_max_life_time"`
	}

	CasbinConfig struct {
		Enabled          bool   `json:"enabled"`
		LogEnabled       bool   `json:"log_enabled"`
		AuthModel        string `json:"auth_model"`
		AutoLoad         bool   `json:"auto_load"`
		AutoLoadInterval int    `json:"auto_load_interval"`
	}

	RootUserConfig struct {
		Username string `json:"username"`
		Password string `json:"password"`
		RealName string `json:"real_name"`
	}

	InitializeConfig struct {
		MenuDataPath string `json:"menu_data_path"`
	}

	ApplicationConfig struct {
		Root       RootUserConfig   `json:"root"`
		Initialize InitializeConfig `json:"initialize"`
	}

	Config struct {
		SwaggerEnabled bool              `json:"swagger_enabled"`
		Application    ApplicationConfig `json:"application"`
		Server         ServerConfig      `json:"server"`
		Casbin         CasbinConfig      `json:"casbin"`
		Logger         LoggerConfig      `json:"logger"`
		MySQL          DBConfig          `json:"mysql"`
		Redis          RedisConfig       `json:"redis"`
		APIToken       string            `json:"api_token"`
		APIHost        string            `json:"api_host"`
	}
)

// LoadFromFile and create config instance,
// if has some error will be returned with nil instance
func LoadFromFile(path string) (*Config, error) {
	bytesBuf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("load config failed: %w", err)
	}

	conf := new(Config)
	if err := json.Unmarshal(bytesBuf, conf); err != nil {
		return nil, fmt.Errorf("unserialization config json failed: %w", err)
	}

	return conf, nil
}

func (c DBConfig) GetDSN() string {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.Username, c.Password, c.Host, c.Port, c.DBName)
	if len(c.Params) > 0 {
		dsn += "?"
		params := make([]string, 0)
		for key, val := range c.Params {
			param := fmt.Sprintf("%s=%s", key, val)
			params = append(params, param)
		}
		paramSuffix := strings.Join(params, "&")
		dsn += paramSuffix
	}
	return dsn
}
